'use strict';

const fs = require('fs');

class loader {

  constructor (options = {}) {
    let appDir = '.';
    
    this.globalMidTable = {};
    this.groupMidTable = {};
    this.fileMidTable = {};

    if (typeof options !== 'object') {
      options = {};
    }
    
    if (options.appPath !== undefined) {
      appDir = options.appPath;
    }

    appDir = fs.realpathSync(appDir);
    this.config = {
      //当作为模块引入时，根据路径关系，
      //可能的位置是node_modules/titbit-loader/loader.js，
      //所以默认在父级目录两层和node_modules同级。
      appPath     : appDir,
      controllerPath  : appDir+'/controller',
      modelPath     : appDir+'/model',
      midwarePath   : appDir+'/middleware',
      loadModel     : true,
      midwareDesc   : appDir+'/midware.js',

      deep : 1,

      mname : 'model',

      postArgs : false,

    };

    //在加载Model时可能需要传递参数
    this.mdb = null;

    //如果你要在初始化controller时传递参数可以设置此变量，但是目前这个功能还没有开启。
    this.cargs = null;

    for (let k in options) {
      
      if (k == 'appPath') { continue; }

      if (k == 'loadModel') {
        this.config.loadModel = options.loadModel;
        continue;
      } else if (k === 'mname') {
        this.config.mname = options.mname;
        continue;
      } else if (k === 'postArgs') {
        this.config.postArgs = options[k];
        continue;
      }

      switch (k) {
        case 'controllerPath':
        case 'modelPath':
        case 'midwarePath':
          this.config[k] = `${this.appPath}/${options[k]}`; break;
        default:;
      }
    }

    try {
      fs.accessSync(this.config.controllerPath, fs.constants.F_OK);
    } catch (err) {
      if (this.config.controllerPath.length > 0) {
        fs.mkdirSync(this.config.controllerPath);
      }
    }

    try {
      fs.accessSync(this.config.midwarePath, fs.constants.F_OK);
    } catch (err) {
      if (this.config.midwarePath.length > 0) {
        fs.mkdirSync(this.config.midwarePath);
      }
    }

    try {
      fs.accessSync(this.config.modelPath, fs.constants.F_OK);
    } catch (err) {
      if (this.config.modelPath.length > 0) {
        fs.mkdirSync(this.config.modelPath);
      }
    }

    if (options.mdb !== undefined && this.config.loadModel) {
      this.mdb = options.mdb;
    }
    
  }

  init (app) {
    this.loadController(app);
    this.loadMidware(app);
    if (this.config.loadModel) {
      this.loadModel(app);
    }
  }

  loadController (app) {
    var cfiles = {};
    this.readControllers(this.config.controllerPath, cfiles);
    let cob = null;
    for (let k in cfiles) {
      try {
        cob = require(k);
        cob = new cob();
        this.setRouter(app, cob, cfiles[k]);
        cob = null;
      } catch (err) {
        console.error(err.message, k);
      }
    }

    return cfiles;
  }

  setRouter (app, cob, cf) {
    if (cob.mode === undefined || cob.mode !== 'callback') {
      cob.mode = 'restful';
    }
    var group = cf.dirgroup;
    var npre = cf.filegroup;
    let routeParam = '/:id';
    if (cob.param !== undefined
      && cob.param !== null
      && typeof cob.param === 'string')
    {
      routeParam = cob.param;
      if (routeParam[0]!== '/') {
        routeParam = `/${routeParam}`;
      }
    }

    if (cob.mode === 'restful') {
      
      if (cob.post !== undefined && typeof cob.post === 'function') {
        app.router.post(`${cf.filegroup}${this.config.postArgs ? routeParam : ''}`, cob.post.bind(cob),{
          name: cob.name_post || `${npre}/post`,
          group: group
        });
      }
      if (cob.delete !== undefined && typeof cob.delete === 'function') {
        app.router.delete(`${cf.filegroup}${routeParam}`, cob.delete.bind(cob),{
          name: cob.name_delete || `${npre}/delete`,
          group: group
        });
      }
      if (cob.put !== undefined && typeof cob.put === 'function') {
        app.router.put(`${cf.filegroup}${routeParam}`, cob.put.bind(cob),{
          name: cob.name_put || `${npre}/put`,
          group: group
        });
      }
      if (cob.get !== undefined && typeof cob.get === 'function') {
        app.router.get(`${cf.filegroup}${routeParam}`, cob.get.bind(cob),{
          name: cob.name_get || `${npre}/get`,
          group: group
        });
      }
      if (cob.list !== undefined && typeof cob.list === 'function') {
        app.router.get(`${cf.filegroup}`, cob.list.bind(cob),{
          name: cob.name_list || `${npre}/list`,
          group: group
        });
      }
      if (cob.patch !== undefined && typeof cob.patch === 'function') {
        app.router.patch(`${cf.filegroup}`, cob.patch.bind(cob),{
          name: cob.name_patch || `${npre}/patch`,
          group: group
        });
      }
      if (cob.options !== undefined && typeof cob.options === 'function') {
        app.router.options(`${cf.filegroup}${routeParam}`, cob.options.bind(cob),{
          name: cob.name_options || `${npre}/options`,
          group: group
        });
      }
    } else {
      if (cob.method === undefined) {
        cob.method = 'GET';
      }
      let cname = `${npre}`;
      switch (cob.method) {
        case 'GET':
        case 'POST':
        case 'DELETE':
        case 'PUT':
        case 'OPTIONS':
        case 'PATCH':
        case 'HEAD':
        case 'TRACE':
          app.router[ cob.method.toLowerCase() ](
            cf.filegroup,
            cob.callback.bind(cob), {
              name: cname,
              group: group
            });
          break;
        default:;
      }
      if (cob.router !== undefined && typeof cob.router === 'object') {
        for (let k in cob.router) {
          if (cob[k] !== undefined && typeof cob[k] === 'function') {
            if (app.router.methods.indexOf(cob.router[k].toUpperCase()) >= 0)
            {
              app.router[cob.router[k].toLowerCase()](cf.filegroup+'/'+k, cob[k]);
            }
          }
        }
      }
    }

    if (cob.__mid && typeof cob.__mid === 'function') {
      var mid = cob.__mid();
      if (mid) {
        this.fileMidTable[cf.filegroup] = {
          group:group,
          mid:mid
        };
      }
    }
  }

  loadMidware (app) {
    for (let i=0; i<this.globalMidTable.length; i++) {
      this.loadGlobalMidware(app, this.globalMidTable[i]);
    }
    //加载组，此时组已经确定
    for (let k in this.groupMidTable) {
      for (let i=0; i<this.groupMidTable[k].length; i++) {
        this.loadGroupMidware(app, this.groupMidTable[k][i], k);
      }
    }

    for(var k in this.fileMidTable) {
      for (let i=0; i<this.fileMidTable[k].mid.length; i++) {
        this.loadFileMidware(app, 
          this.fileMidTable[k].mid[i], k, 
          this.fileMidTable[k].group
        );
      }
    }

  }

  getMidwareInstance(m) {
    var mt = null;
    let tmp = null;
    if (m.mid[0] == '@') {
      tmp = require(this.config.midwarePath+'/'+m.mid.substring(1));
      if (m.args === undefined) {
        mt = new tmp();
      } else {
        mt = new tmp(m.args);
      }
      //bind this
      return mt.middleware.bind(mt);
    } else {
      mt = require(this.config.midwarePath+'/'+m.mid);
    }
    return mt;
  }

  loadGlobalMidware (app, m) {
    if (!m.mid || m.mid == '') {
      return;
    }

    if (m.filter === undefined) {
      m.filter = null;
    }

    if (m.pre === undefined) {
      m.pre = false;
    }

    let group = m.group || null;

    let mware = this.getMidwareInstance(m);

    let addfunc = (grp = null) => {
      if (m.pre) {
        app.pre(mware, m.filter, grp);
      } else {
        app.use(mware, m.filter, grp);
      }
    };

    if (group instanceof Array) {
      for (let i = 0 ; i < group.length; i++) {
        addfunc(group[i]);
      }
    } else {
      addfunc(group);
    }
    
  }

  loadGroupMidware(app, m, group) {
    if (!m.mid || m.mid == '') {
      return;
    }

    if (m.filter === undefined) {
      m.filter = null;
    }

    let ware = this.getMidwareInstance(m);

    if (m.pre !== undefined && m.pre) {
      app.pre(ware, m.filter, group);
    } else {
      app.use(ware, m.filter, group);
    }

  }

  loadFileMidware (app, m, f, group) {

    if (!m.mid) {
      return;
    }

    let path = [];
    if (m.methods === undefined) {
      path = [
        `${f}/post`, `${f}/put`,`${f}/delete`,`${f}/get`, `${f}/list`, 
        `${f}/options`, `${f}/patch`, `${f}/head`, `${f}/callback`
      ];
    } else if (typeof m.methods === 'string') {
      path.push(`${f}/${m.methods}`);
    }

    let filter = (ctx) => {
      if (path.indexOf(ctx.name) < 0) {
        return false;
      }
      return true;
    };

    let ware = this.getMidwareInstance(m);

    if (m.pre !== undefined && m.pre) {
      app.pre(ware, filter, group);
    } else {
      app.use(ware, filter, group);
    }

  }

  /**
   * 加载数据库操作接口，一个表要对应一个js文件，
   * 默认没有模型关联的支持，这需要自己编写SQL语句。
   */
  loadModel (app) {
    if (app.service[this.config.mname] === undefined) {
      app.service[this.config.mname] = {};
    }
    try {
      var mlist = fs.readdirSync(this.config.modelPath, {withFileTypes:true});
      for (let i=0; i < mlist.length; i++) {
        if (!mlist[i].isFile()) { continue; }
        if (mlist[i].name.substring(mlist[i].name.length-3) !== '.js') {
          continue;
        }
        this.requireModel(app, mlist[i].name);
      }
    } catch (err) {
      console.error(err);
    }
  }

  requireModel(app, mfile) {
    try {
      let m = require(this.config.modelPath+'/'+mfile);
      let mname = mfile.substring(0, mfile.length-3);
      app.service[this.config.mname][mname] = new m(this.mdb);
    } catch (err) {
      console.error(err.message, ' -- ', mfile);
    }
  }

  stripExtName (filename) {
    let sf = filename.split('.js');
    return `${sf[0]}`;
  }

  /**
   * 读取控制器目录中的文件
   * @param {string} cdir 
   * @param {object} cfiles 
   * @param {number} deep 
   * @param {string} dirgroup 
   */
  readControllers (cdir, cfiles, deep = 0, dirgroup = '') {
    let files = fs.readdirSync(cdir, {withFileTypes:true});

    let tmp = '';
    for (let i=0; i<files.length; i++) {

      if (files[i].isDirectory() && deep < 1) {

        if (files[i].name[0] == '!') { continue; }

        this.readControllers(cdir+'/'+files[i].name, 
          cfiles, deep+1,
          `${dirgroup}/${files[i].name}`
        );

      } else if (files[i].isFile()) {
        if (files[i].name[0] == '!') {continue;}

        if (files[i].name.length < 4) { continue; }

        if (files[i].name.indexOf('.js') !== files[i].name.length - 3) {
          continue;
        }

        if (files[i].name == '__mid.js') {
          if (deep == 0) {
            this.globalMidTable = require(cdir+'/'+files[i].name);
          } else {
            this.groupMidTable[dirgroup] = require(cdir+'/'+files[i].name);
          }
          continue;
        }

        tmp = this.stripExtName(files[i].name);
        cfiles[cdir+'/'+files[i].name] = {
          filegroup: dirgroup + '/' + tmp,
          dirgroup: dirgroup || '/',
          name: files[i].name,
          modname: tmp
        };
      }
    }
    
  }

}

module.exports = loader;
